#ifndef SIMULATION_CLIENT_CLIENT_H_
#define SIMULATION_CLIENT_CLIENT_H_

#include <functional>
#include <string>
#include <tuple>
#include <vector>

#include "detail/ffi.rs.h"

namespace simulation {
namespace client {

using TestResult = ::ffi::TestResult;

using LocalizationInfo = ::ffi::LocalizationInfo;

using PerceptionInfo = ::ffi::PerceptionInfo;

using ChassisInfo = ::ffi::ChassisInfo;

using CollisionEvent = ::ffi::CollisionEvent;

using LaneInvasionEvent = ::ffi::LaneInvasionEvent;

using Image = ::ffi::Image;

using VehicleControl = ::ffi::VehicleControl;

struct Callbacks {
  std::function<void(const TestResult&)> on_test_result =
      [](const TestResult&) {};

  std::function<void(const PerceptionInfo&)> on_perception =
      [](const PerceptionInfo&) {};

  std::function<void(const LocalizationInfo&)> on_localization =
      [](const LocalizationInfo&) {};

  std::function<void(const ChassisInfo&)> on_chassis = [](const ChassisInfo&) {
  };

  std::function<void(const CollisionEvent&)> on_collision_event =
      [](const CollisionEvent&) {};

  std::function<void(const LaneInvasionEvent&)> on_lane_invasion_event =
      [](const LaneInvasionEvent&) {};

  std::function<void(const std::vector<Image>&)> on_camera_images =
      [](const std::vector<Image>&) {};
};

struct ClientInner;

class Client {
 public:
  Client();
  Client(Client&&);

  ~Client();

  Client& operator=(Client&&);

  Client(const Client&) = delete;
  Client& operator=(const Client&) = delete;

  std::tuple<bool, std::string> Connect(const std::string& uri,
                                        unsigned int timeout_ms = 5000);

  std::tuple<bool, std::string> Run(const std::string& client_id,
                                    const std::string& vehicle_type, double x,
                                    double y, double z, double qw, double qx,
                                    double qy, double qz,
                                    const Callbacks& callbacks);

  std::tuple<bool, std::string> Run(const std::string& client_id,
                                    const std::string& token,
                                    const Callbacks& callbacks);

  std::tuple<bool, std::string> SendControlCommand(
      const VehicleControl& control);

  std::tuple<bool, std::string> SetVehicleLocalization(
      double pos_x, double pos_y, double pos_z, double ori_qw, double ori_qx,
      double ori_qy, double ori_qz);

  // 在Run()结束之前才能调用,推荐在回调函数中调用
  void Stop();

 private:
  std::tuple<bool, std::string> Run(const Callbacks& callbacks);

 private:
  ClientInner* inner_ = nullptr;
};

}  // namespace client
}  // namespace simulation

#endif
